<!-- 数字滚动 -->
<template>
  <span class="art-count-to" :class="{ 'is-running': isRunning }">
    {{ formattedValue }}
  </span>
</template>

<script setup lang="ts">
import { computed, watch, nextTick, onUnmounted, shallowRef } from "vue";
import { useTransition, TransitionPresets } from "@vueuse/core";

// 类型定义
interface CountToProps {
  /** 目标值 */
  target: number;
  /** 动画持续时间（毫秒） */
  duration?: number;
  /** 是否自动开始 */
  autoStart?: boolean;
  /** 小数位数 */
  decimals?: number;
  /** 小数点符号 */
  decimal?: string;
  /** 千分位分隔符 */
  separator?: string;
  /** 前缀 */
  prefix?: string;
  /** 后缀 */
  suffix?: string;
  /** 缓动函数 */
  easing?: keyof typeof TransitionPresets;
  /** 是否禁用动画 */
  disabled?: boolean;
}

interface CountToEmits {
  started: [value: number];
  finished: [value: number];
  paused: [value: number];
  reset: [];
}

interface CountToExpose {
  start: (target?: number) => void;
  pause: () => void;
  reset: (newTarget?: number) => void;
  stop: () => void;
  setTarget: (target: number) => void;
  readonly isRunning: boolean;
  readonly isPaused: boolean;
  readonly currentValue: number;
  readonly targetValue: number;
  readonly progress: number;
}

// 常量定义
const EPSILON = Number.EPSILON;
const MIN_DURATION = 100;
const MAX_DURATION = 60000;
const MAX_DECIMALS = 10;
const DEFAULT_EASING = "easeOutExpo";
const DEFAULT_DURATION = 2000;

const props = withDefaults(defineProps<CountToProps>(), {
  target: 0,
  duration: DEFAULT_DURATION,
  autoStart: true,
  decimals: 0,
  decimal: ".",
  separator: "",
  prefix: "",
  suffix: "",
  easing: DEFAULT_EASING,
  disabled: false,
});

const emit = defineEmits<CountToEmits>();

// 工具函数
const validateNumber = (
  value: number,
  name: string,
  defaultValue: number,
): number => {
  if (!Number.isFinite(value)) {
    console.warn(`[CountTo] Invalid ${name} value:`, value);
    return defaultValue;
  }
  return value;
};

const clamp = (value: number, min: number, max: number): number => {
  return Math.max(min, Math.min(value, max));
};

const formatNumber = (
  value: number,
  decimals: number,
  decimal: string,
  separator: string,
): string => {
  let result =
    decimals > 0 ? value.toFixed(decimals) : Math.floor(value).toString();

  // 处理小数点符号
  if (decimal !== "." && result.includes(".")) {
    result = result.replace(".", decimal);
  }

  // 处理千分位分隔符
  if (separator) {
    const parts = result.split(decimal);
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, separator);
    result = parts.join(decimal);
  }

  return result;
};

// 安全计算值
const safeTarget = computed(() => validateNumber(props.target, "target", 0));
const safeDuration = computed(() =>
  clamp(
    validateNumber(props.duration, "duration", DEFAULT_DURATION),
    MIN_DURATION,
    MAX_DURATION,
  ),
);
const safeDecimals = computed(() =>
  clamp(validateNumber(props.decimals, "decimals", 0), 0, MAX_DECIMALS),
);
const safeEasing = computed(() => {
  const easing = props.easing;
  if (!(easing in TransitionPresets)) {
    console.warn("[CountTo] Invalid easing value:", easing);
    return DEFAULT_EASING;
  }
  return easing;
});

// 状态管理
const currentValue = shallowRef(0);
const targetValue = shallowRef(safeTarget.value);
const isRunning = shallowRef(false);
const isPaused = shallowRef(false);
const pausedValue = shallowRef(0);

// 动画控制
const transitionValue = useTransition(currentValue, {
  duration: safeDuration,
  transition: computed(() => TransitionPresets[safeEasing.value]),
  onStarted: () => {
    isRunning.value = true;
    isPaused.value = false;
    emit("started", targetValue.value);
  },
  onFinished: () => {
    isRunning.value = false;
    isPaused.value = false;
    emit("finished", targetValue.value);
  },
});

// 格式化显示值
const formattedValue = computed(() => {
  const value = isPaused.value ? pausedValue.value : transitionValue.value;

  if (!Number.isFinite(value)) {
    return `${props.prefix}0${props.suffix}`;
  }

  const formattedNumber = formatNumber(
    value,
    safeDecimals.value,
    props.decimal,
    props.separator,
  );
  return `${props.prefix}${formattedNumber}${props.suffix}`;
});

// 私有方法
const shouldSkipAnimation = (target: number): boolean => {
  const current = isPaused.value ? pausedValue.value : transitionValue.value;
  return Math.abs(current - target) < EPSILON;
};

const resetPauseState = (): void => {
  isPaused.value = false;
  pausedValue.value = 0;
};

// 公共方法
const start = (target?: number): void => {
  if (props.disabled) {
    console.warn("[CountTo] Animation is disabled");
    return;
  }

  const finalTarget = target !== undefined ? target : targetValue.value;

  if (!Number.isFinite(finalTarget)) {
    console.warn("[CountTo] Invalid target value for start:", finalTarget);
    return;
  }

  targetValue.value = finalTarget;

  if (shouldSkipAnimation(finalTarget)) {
    return;
  }

  // 从暂停值开始（如果存在）
  if (isPaused.value) {
    currentValue.value = pausedValue.value;
    resetPauseState();
  }

  nextTick(() => {
    currentValue.value = finalTarget;
  });
};

const pause = (): void => {
  if (!isRunning.value || isPaused.value) {
    return;
  }

  isPaused.value = true;
  pausedValue.value = transitionValue.value;
  currentValue.value = pausedValue.value;

  emit("paused", pausedValue.value);
};

const reset = (newTarget = 0): void => {
  const target = validateNumber(newTarget, "reset target", 0);

  currentValue.value = target;
  targetValue.value = target;
  resetPauseState();

  emit("reset");
};

const setTarget = (target: number): void => {
  if (!Number.isFinite(target)) {
    console.warn("[CountTo] Invalid target value for setTarget:", target);
    return;
  }

  targetValue.value = target;

  if ((isRunning.value || props.autoStart) && !props.disabled) {
    start(target);
  }
};

const stop = (): void => {
  if (isRunning.value || isPaused.value) {
    currentValue.value = 0;
    resetPauseState();
    emit("paused", 0);
  }
};

// 监听器
watch(
  safeTarget,
  (newTarget) => {
    if (props.autoStart && !props.disabled) {
      start(newTarget);
    } else {
      targetValue.value = newTarget;
    }
  },
  { immediate: props.autoStart && !props.disabled },
);

watch(
  () => props.disabled,
  (disabled) => {
    if (disabled && isRunning.value) {
      stop();
    }
  },
);

// 清理
onUnmounted(() => {
  if (isRunning.value) {
    stop();
  }
});

// 暴露 API
defineExpose<CountToExpose>({
  start,
  pause,
  reset,
  stop,
  setTarget,
  get isRunning() {
    return isRunning.value;
  },
  get isPaused() {
    return isPaused.value;
  },
  get currentValue() {
    return isPaused.value ? pausedValue.value : transitionValue.value;
  },
  get targetValue() {
    return targetValue.value;
  },
  get progress() {
    const current = isPaused.value ? pausedValue.value : transitionValue.value;
    const target = targetValue.value;
    if (target === 0) return current === 0 ? 1 : 0;
    return Math.abs(current / target);
  },
});
</script>

<style lang="scss" scoped>
.art-count-to {
  color: var(--art-gray-800);

  &.is-running {
    transition: opacity 0.3s ease;
  }
}
</style>
